package com.smsc.headend.connector.builder.impl;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.NumberUtil;
import com.google.common.collect.Sets;
import com.smsc.headend.common.constant.DeviceModel;
import com.smsc.headend.common.enums.DeviceType;
import com.smsc.headend.common.enums.errorcode.DeviceConnectErrorCode;
import com.smsc.headend.connector.bo.AssociationState;
import com.smsc.headend.connector.bo.UnwrappedPacket;
import com.smsc.headend.connector.builder.AssociationBuilder;
import com.smsc.headend.connector.builder.LoggerBuilder;
import com.smsc.headend.connector.exception.UDISDeviceConException;
import com.smsc.headend.connector.service.*;
import com.smsc.headend.connector.service.protocol.DLMSPacketCodecService;
import com.smsc.headend.connector.utils.CosemXMLUtil;
import com.smsc.headend.connector.utils.GxAddrUtil;
import com.smsc.headend.connector.utils.LogFileNameUtil;
import com.smsc.headend.module.asset.dmo.ComConfigDMO;
import com.smsc.headend.module.asset.dmo.DLMSConfigDMO;
import com.smsc.headend.module.asset.entity.Meter;
import com.smsc.headend.module.asset.entity.ComEntity;
import com.smsc.headend.module.asset.entity.PlcMeterStatus;
import com.smsc.headend.module.data.dto.DLMSSubDataItemDTO;
import com.smsc.headend.module.data.dto.DataItemDTO;
import com.smsc.headend.module.data.query.ModelDataItemQueryItem;
import com.smsc.headend.module.protocol.cosem.bo.CosemData;
import com.smsc.headend.module.protocol.cosem.util.ByteUtils;
import com.smsc.headend.module.protocol.cosem.util.CosemXmlUtils;
import com.smsc.headend.module.task.enums.DeviceActionType;
import gurux.dlms.*;
import gurux.dlms.enums.Conformance;
import gurux.dlms.enums.InterfaceType;
import gurux.dlms.enums.ObjectType;
import gurux.dlms.secure.GXDLMSSecureClient;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.set.UnmodifiableSet;
import org.assertj.core.util.Lists;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;

/**
 * Wiston meter build AA process
 * 1. get Invocation Counter
 * 2. HLS
 *
 * @author liangli
 * @date 2020/8/30
 */
@Slf4j
public class HLSAssociationBuilder implements AssociationBuilder {
    @Autowired
    TerminalChannelService terminalChannelService;
    @Autowired
    DeviceConnectService deviceConnectService;
    @Autowired
    DLMSPacketCodecService packetCodecService;
    @Autowired
    FeignAssetManagementService assetManagementService;
    @Autowired
    DeviceStatusService deviceStatusService;
    @Autowired
    LogFileNameUtil logFileNameUtil;
    @Value("${device.association.connection-timeout:60}")
    String connectionTimeout;
    /**
     * 设备建立Low Security AA获取 设备上存储的 Invocation Counter时使用 Server Wport为16的型号清单
     */
    @Value("${device.association.hls.icSap16:2201,2202,2203,2204,2205,2217,2221,2223,2224,2225}")
    Integer[] icSAP16Model;
    /**
     * 建立HLS aa 不需要获取 设备上存储的 Invocation Counter的设备清单，目前只有DLMS-Gateway，(威胜DCU和威斯顿DCU)
     */
    @Value("${device.association.hls.icSkip:3001,2200,2220}")
    Integer[] icGetSkipModel;

    Integer connectionTimeoutSec;

    Integer invocationCounter = 1;

    private GXDLMSClient lowClient = new GXDLMSClient();

    public enum HLSState {
        snrmRequest,
        llsCreate,
        getIC,
        llsRelease,
        hlsCreate,
        challenge
    }

    @Override
    public byte[] nextPacket(ComEntity com, AssociationState associationState) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException, UDISDeviceConException, SignatureException {
        Integer currentStep = associationState.getStepNumber();
        if (currentStep == null) {
            return getInitRequest(associationState);
        }
        HLSState state = Arrays.stream(HLSState.values()).filter(s -> s.ordinal() == currentStep).findFirst().get();
        switch (state) {
            case snrmRequest:
                return getRequestAfterSnrm(associationState);
            case llsCreate:
                return getICRequest(com, associationState);
            case getIC:
                return getLowAssociationRelease(associationState);
            case llsRelease:
                return getHighAssociateRequest(associationState, ++this.invocationCounter);
            case hlsCreate:
                return getChallengeRequest(associationState, ++this.invocationCounter);
            case challenge:
                return null;
        }
        return null;
    }

    @Override
    public Boolean acceptPacket(ComEntity terminal, byte[] byteReceive, AssociationState associationState) throws UDISDeviceConException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException, SignatureException {
        Integer currentStep = associationState.getStepNumber();
        HLSState state = Arrays.stream(HLSState.values()).filter(s -> s.ordinal() == currentStep).findFirst().get();
        Boolean hasNext = true;
        switch (state) {
            case snrmRequest:
                snrmResponse(byteReceive, associationState);
                break;
            case llsCreate:
                lowAssociationResponse(terminal, byteReceive);
                break;
            case getIC:
                icGetResponse(byteReceive);
                break;
            case llsRelease:
                llsReleaseResponse(terminal);
                break;
            case hlsCreate:
                hlsAssociationResponse(byteReceive, associationState, terminal);
                break;
            case challenge:
                doChallengeResponse(byteReceive, associationState);
                hasNext = false;
                break;
        }
        return hasNext;
    }

    private void doChallengeResponse(byte[] byteReceive, AssociationState associationState) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException, SignatureException {
        UnwrappedPacket unwrappedPacket = packetCodecService.unwrapPrefixPacket(byteReceive);
        GXByteBuffer buffer = new GXByteBuffer();
        GXReplyData replyData = new GXReplyData();
        buffer.set(unwrappedPacket.getPacket());
        associationState.getClient().getData(buffer, replyData);
        associationState.getClient().parseApplicationAssociationResponse(replyData.getData());
    }

    private void hlsAssociationResponse(byte[] byteReceive, AssociationState associationState, ComEntity terminal) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException, UDISDeviceConException {
        UnwrappedPacket unwrappedPacket = packetCodecService.unwrapPrefixPacket(byteReceive);

        GXByteBuffer buffer = new GXByteBuffer();
        GXReplyData replyData = new GXReplyData();
        buffer.set(unwrappedPacket.getPacket());
        associationState.getClient().getData(buffer, replyData);
        LoggerBuilder.getLogger(logFileNameUtil.getLogFileName(terminal), this.getClass()).info("aa {}", ByteUtils.byteToHexString(replyData.getData().getData()));
        Boolean unpredictableException = false;
        try {
            associationState.getClient().parseAareResponse(replyData.getData());
            if (!"00".equalsIgnoreCase(CosemXMLUtil.getAssociationResult(CosemXMLUtil.translator.messageToXml(unwrappedPacket.getPacket())))) {
                throw new UDISDeviceConException(DeviceConnectErrorCode.AARE_AUTH_FAIL.getErrorText());
            }
        } catch (Exception e) {
            LoggerBuilder.getLogger(logFileNameUtil.getLogFileName(terminal), this.getClass()).error("HLS aa failed", e);
            unpredictableException = true;
        }
        if (unpredictableException) {
            throw new UDISDeviceConException(DeviceConnectErrorCode.AARE_AUTH_FAIL.getErrorText());
        }
        LoggerBuilder.getLogger(logFileNameUtil.getLogFileName(terminal), this.getClass()).info("parse aa re over , start doChallenge ", associationState.getClient().getIsAuthenticationRequired());
    }

    private void llsReleaseResponse(ComEntity terminal) {
        LoggerBuilder.getLogger(logFileNameUtil.getLogFileName(terminal), this.getClass()).info("lls release success");
    }

    private void icGetResponse(byte[] byteReceive) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
        UnwrappedPacket unwrappedPacket = packetCodecService.unwrapPrefixPacket(byteReceive);
        GXByteBuffer buffer = new GXByteBuffer();
        GXReplyData replyData = new GXReplyData();
        buffer.set(unwrappedPacket.getPacket());

        lowClient.getData(buffer, replyData);
        GXDLMSTranslator translator = new GXDLMSTranslator(TranslatorOutputType.SIMPLE_XML);
        String resultString = translator.dataToXml(replyData.getData());
        CosemData data = CosemXmlUtils.translateCosemXml(resultString);
        this.invocationCounter = Integer.parseInt(data.getValue(), 16);
    }

    private void lowAssociationResponse(ComEntity terminal, byte[] bytes) throws UDISDeviceConException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
        UnwrappedPacket unwrappedPacket = packetCodecService.unwrapPrefixPacket(bytes);
        GXByteBuffer buffer = new GXByteBuffer();
        GXReplyData gxReplyData = new GXReplyData();
        buffer.set(unwrappedPacket.getPacket());
        lowClient.getData(buffer, gxReplyData);
        Boolean unpredictableException = false;
        try {
            lowClient.parseAareResponse(gxReplyData.getData());
            String pduXml = packetCodecService.getPduXml();
            if (!"00".equalsIgnoreCase(CosemXMLUtil.getAssociationResult(pduXml))) {
                throw new UDISDeviceConException(DeviceConnectErrorCode.AARE_AUTH_FAIL.getErrorText());
            }
        } catch (Exception e) {
            LoggerBuilder.getLogger(logFileNameUtil.getLogFileName(terminal), this.getClass()).error("LLS aa failed, {}", ByteUtils.byteToHexString(unwrappedPacket.getPacket()), e);
            unpredictableException = true;
        }
        if (unpredictableException) {
            throw new UDISDeviceConException(DeviceConnectErrorCode.AARE_AUTH_FAIL.getErrorText());
        }
    }

    private void snrmResponse(byte[] aare, AssociationState associationState) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
        UnwrappedPacket packet = packetCodecService.unwrapPrefixPacket(aare);
        GXReplyData replyData = new GXReplyData();
        GXByteBuffer gxByteBuffer = new GXByteBuffer();
        gxByteBuffer.set(packet.getPacket());
        associationState.getClient().getData(gxByteBuffer, replyData);
        associationState.getClient().parseUAResponse(replyData.getData());
    }

    @Override
    public void buildAssociation(ComEntity terminal, Meter meter, Channel channel, AssociationState context) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, InterruptedException, UDISDeviceConException, SignatureException {
        this.connectionTimeoutSec = NumberUtil.isNumber(connectionTimeout) ? Integer.parseInt(connectionTimeout) : 60;
        DLMSConfigDMO dlmsConfigDMO = packetCodecService.getDLMSConfigDMO(terminal, meter);
        if (dlmsConfigDMO != null && dlmsConfigDMO.getInterfaceType().equalsIgnoreCase(InterfaceType.HDLC.name())) {
            // 46 HDLC需要发送snrm request
            sendSnrmRequest(terminal, meter, channel, context);
        }
        boolean doIcGet = true;
        if (meter == null &&
                Stream.of(icGetSkipModel).filter(i -> i.equals(((ComConfigDMO) dlmsConfigDMO).getModelId())).findAny().isPresent()
        ) {
            doIcGet = false;
        }
        String deviceAddress = getDeviceAddress(terminal, meter, dlmsConfigDMO);
        Integer invocationCounter = 1;
        if (doIcGet) {
            createAssociation(terminal, meter, channel, dlmsConfigDMO, deviceAddress);
            invocationCounter = getInvocationCounter(terminal, meter, channel, context, dlmsConfigDMO, deviceAddress);
            releaseAssociation(terminal, meter, channel, deviceAddress);
        } else {
            log.info("skip invocation counter get from COSEM device");
        }
        createHLSAssociation(invocationCounter, terminal, meter, channel, context, deviceAddress);
    }

    private String getDeviceAddress(ComEntity terminal, Meter meter, DLMSConfigDMO dlmsConfigDMO) {
        if (meter == null) {
            return null;
        }
        //Com是电表，
        if (terminal.getComDeviceType().equals(DeviceType.METER.getId()) || terminal.getComDeviceType().equals(DeviceType.GPRS.getId())) {
            //HDLC 协议使用的  deviceAddress需要计算
            if (dlmsConfigDMO.getInterfaceType().equalsIgnoreCase(InterfaceType.HDLC.name())) {
                return Convert.toStr(GxAddrUtil.getHDLCServerAddress(GxAddrUtil.setDesId(meter.getDeviceAddr())));
            } else {
                return null;
            }
        }
        //通过dcu下透传的电表地址
        return meter.getMfgSn();
    }

    public void sendSnrmRequest(ComEntity terminal, Meter meter, Channel channel, AssociationState aaState) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, UDISDeviceConException, InterruptedException {
        byte[] snrmRequest = aaState.getClient().snrmRequest();

        String deviceAddress = meter == null ? null : meter.getMfgSn();
        snrmRequest = packetCodecService.wrapPrefixToPacket(snrmRequest, terminal, deviceAddress);
        deviceConnectService.sendPacket2Channel(channel, snrmRequest);
        //wait 60 s for aare
        byte[] aare = terminalChannelService.getSyncResponse(channel, this.connectionTimeoutSec, deviceAddress);
        if (aare != null && aare.length > 0) {
            UnwrappedPacket packet = packetCodecService.unwrapPrefixPacket(aare);
            GXReplyData replyData = new GXReplyData();
            GXByteBuffer gxByteBuffer = new GXByteBuffer();
            gxByteBuffer.set(packet.getPacket());
            aaState.getClient().getData(gxByteBuffer, replyData);
            aaState.getClient().parseUAResponse(replyData.getData());
        } else {
            throw new UDISDeviceConException("build snrm request fail");
        }
    }

    private void createHLSAssociation(Integer invocationCounter, ComEntity terminal, Meter meter, Channel channel, AssociationState context, String deviceAddress) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, InterruptedException, SignatureException, UDISDeviceConException {
        LoggerBuilder.getLogger(logFileNameUtil.getLogFileName(terminal), this.getClass()).info("start hls aa build");
        if (context.getClient() instanceof GXDLMSSecureClient) {
            //  counter 每次必须比表内读出来的大 这里加1
            ((GXDLMSSecureClient) context.getClient()).getCiphering().setInvocationCounter(invocationCounter + 1);
        }

        byte[] aa = context.getClient().aarqRequest()[0];
        LoggerBuilder.getLogger(logFileNameUtil.getLogFileName(terminal), this.getClass()).info("aa created");
        aa = packetCodecService.wrapPrefixToPacket(aa, terminal, deviceAddress);
        deviceConnectService.sendPacket2Channel(channel, aa);
        byte[] bytes = terminalChannelService.getSyncResponse(channel, this.connectionTimeoutSec, deviceAddress);
        if (bytes == null) {
            throw new UDISDeviceConException(DeviceConnectErrorCode.AARQ_TIME_OUT.getErrorText());
        }
        UnwrappedPacket unwrappedPacket = packetCodecService.unwrapPrefixPacket(bytes);

        GXByteBuffer buffer = new GXByteBuffer();
        GXReplyData replyData = new GXReplyData();
        buffer.set(unwrappedPacket.getPacket());
        context.getClient().getData(buffer, replyData);
        LoggerBuilder.getLogger(logFileNameUtil.getLogFileName(terminal), this.getClass()).info("aa {}", ByteUtils.byteToHexString(replyData.getData().getData()));
        Boolean unpredictableException = false;
        try {
            context.getClient().parseAareResponse(replyData.getData());
            if (!"00".equalsIgnoreCase(CosemXMLUtil.getAssociationResult(CosemXMLUtil.translator.messageToXml(unwrappedPacket.getPacket())))) {
                throw new UDISDeviceConException(DeviceConnectErrorCode.AARE_AUTH_FAIL.getErrorText());
            }
        } catch (Exception e) {
            LoggerBuilder.getLogger(logFileNameUtil.getLogFileName(terminal), this.getClass()).error("HLS aa failed", e);
            unpredictableException = true;
        }
        if (unpredictableException) {
            throw new UDISDeviceConException(DeviceConnectErrorCode.AARE_AUTH_FAIL.getErrorText());
        }
        LoggerBuilder.getLogger(logFileNameUtil.getLogFileName(terminal), this.getClass()).info("parse aa re over , start doChallenge ", context.getClient().getIsAuthenticationRequired());
        this.doChallenge(terminal, meter, channel, context, deviceAddress);
    }

    private void doChallenge(ComEntity terminal, Meter meter, Channel channel, AssociationState context, String deviceAddress) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, SignatureException, InvalidKeyException, InterruptedException, UDISDeviceConException {
        LoggerBuilder.getLogger(logFileNameUtil.getLogFileName(terminal), this.getClass()).info("start hls do challenge");
        if (context.getClient() instanceof GXDLMSSecureClient) {
            //  counter 每次必须比表内读出来的大 这里加1
            Long invocationCounter = ((GXDLMSSecureClient) context.getClient()).getCiphering().getInvocationCounter();
            ((GXDLMSSecureClient) context.getClient()).getCiphering().setInvocationCounter(invocationCounter + 1);
        }
        byte[] challenge = context.getClient().getApplicationAssociationRequest()[0];
        challenge = packetCodecService.wrapPrefixToPacket(challenge, terminal, deviceAddress);
        deviceConnectService.sendPacket2Channel(channel, challenge);
        byte[] bytes = terminalChannelService.getSyncResponse(channel, this.connectionTimeoutSec, deviceAddress);
        if (bytes == null) {
            throw new UDISDeviceConException(DeviceConnectErrorCode.AARQ_TIME_OUT.getErrorText());
        }
        UnwrappedPacket unwrappedPacket = packetCodecService.unwrapPrefixPacket(bytes);
        GXByteBuffer buffer = new GXByteBuffer();
        GXReplyData replyData = new GXReplyData();
        buffer.set(unwrappedPacket.getPacket());
        context.getClient().getData(buffer, replyData);
        context.getClient().parseApplicationAssociationResponse(replyData.getData());
    }

    private void releaseAssociation(ComEntity terminal, Meter meter, Channel channel, String deviceAddress) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, InterruptedException, UDISDeviceConException {
        byte[] aa = lowClient.releaseRequest()[0];
        aa = packetCodecService.wrapPrefixToPacket(aa, terminal, deviceAddress);
        deviceConnectService.sendPacket2Channel(channel, aa);
        byte[] bytes = terminalChannelService.getSyncResponse(channel, this.connectionTimeoutSec, deviceAddress);
        if (bytes == null) {
            throw new UDISDeviceConException(DeviceConnectErrorCode.AARQ_TIME_OUT.getErrorText());
        }

        UnwrappedPacket unwrappedPacket = packetCodecService.unwrapPrefixPacket(bytes);
        GXByteBuffer buffer = new GXByteBuffer();
        buffer.set(unwrappedPacket.getPacket());
    }

    private void createAssociation(ComEntity terminal, Meter meter, Channel channel, DLMSConfigDMO dlmsConfigDMO, String deviceAddress) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, InterruptedException, UDISDeviceConException {
        if (null != meter && Stream.of(icSAP16Model).filter(i -> i.equals(meter.getModelId())).findAny().isPresent()) {
            lowClient.setServerAddress(1);
            lowClient.setClientAddress(16);
        } else {
            lowClient.setServerAddress(1);
            lowClient.setClientAddress(1);
        }
        lowClient.setInterfaceType(InterfaceType.valueOf(dlmsConfigDMO.getInterfaceType()));
        lowClient.setUseLogicalNameReferencing(!dlmsConfigDMO.getIsShortName());
        lowClient.setMaxReceivePDUSize(0x400);
        Set<Conformance> conformanceList = Sets.newHashSet(Conformance.EVENT_NOTIFICATION, Conformance.ACTION, Conformance.SELECTIVE_ACCESS,
                Conformance.SET, Conformance.GET, Conformance.MULTIPLE_REFERENCES, Conformance.BLOCK_TRANSFER_WITH_ACTION,
                Conformance.BLOCK_TRANSFER_WITH_GET_OR_READ, Conformance.BLOCK_TRANSFER_WITH_SET_OR_WRITE, Conformance.ATTRIBUTE_0_SUPPORTED_WITH_GET
                , Conformance.PRIORITY_MGMT_SUPPORTED);

        lowClient.setProposedConformance(conformanceList);
        byte[] aa = lowClient.aarqRequest()[0];
        aa = packetCodecService.wrapPrefixToPacket(aa, terminal, deviceAddress);
        deviceConnectService.sendPacket2Channel(channel, aa);
        byte[] bytes = terminalChannelService.getSyncResponse(channel, this.connectionTimeoutSec, deviceAddress);
        if (bytes == null) {
            throw new UDISDeviceConException(DeviceConnectErrorCode.AARQ_TIME_OUT.getErrorText());
        }
        UnwrappedPacket unwrappedPacket = packetCodecService.unwrapPrefixPacket(bytes);
        GXByteBuffer buffer = new GXByteBuffer();
        GXReplyData gxReplyData = new GXReplyData();
        buffer.set(unwrappedPacket.getPacket());
        lowClient.getData(buffer, gxReplyData);
        Boolean unpredictableException = false;
        try {
            lowClient.parseAareResponse(gxReplyData.getData());
            if (!"00".equalsIgnoreCase(CosemXMLUtil.getAssociationResult(CosemXMLUtil.translator.pduToXml(gxReplyData.getData().toString())))) {
                throw new UDISDeviceConException(DeviceConnectErrorCode.AARE_AUTH_FAIL.getErrorText());
            }
        } catch (Exception e) {
            LoggerBuilder.getLogger(logFileNameUtil.getLogFileName(terminal), this.getClass()).error("LLS aa failed, {}", ByteUtils.byteToHexString(unwrappedPacket.getPacket()), e);
            unpredictableException = true;
        }
        if (unpredictableException) {
            throw new UDISDeviceConException(DeviceConnectErrorCode.AARE_AUTH_FAIL.getErrorText());
        }
        LoggerBuilder.getLogger(logFileNameUtil.getLogFileName(terminal), this.getClass()).info("get hls secret association success");
    }

    private Integer getInvocationCounter(ComEntity terminal, Meter meter, Channel channel, AssociationState context, DLMSConfigDMO dlmsConfigDMO, String deviceAddress) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, InterruptedException, UDISDeviceConException {
        ModelDataItemQueryItem queryItem = new ModelDataItemQueryItem();
        queryItem.setDeviceActionType(DeviceActionType.Get.getCode());
        queryItem.setModelId(meter == null ? ((ComConfigDMO) dlmsConfigDMO).getModelId() : meter.getModelId());
        queryItem.setDataItemIds(Lists.newArrayList(context.getIcSubDataItemId()));
        LoggerBuilder.getLogger(logFileNameUtil.getLogFileName(terminal), this.getClass()).info("try get IC by subDataItemId {}", context.getIcSubDataItemId());
        List<DataItemDTO> dataItemDTOList = assetManagementService.queryModelDataItemCosem(queryItem);
        DataItemDTO icDataItem = dataItemDTOList.get(0);
        if (!DLMSSubDataItemDTO.class.isAssignableFrom(icDataItem.getClass())) {
            throw new UDISDeviceConException(DeviceConnectErrorCode.HLS_GET_INVOCATION_COUNTER_FAILED.getErrorText());
        }
        DLMSSubDataItemDTO dlmsIvkCnterDTO = (DLMSSubDataItemDTO) icDataItem;
        byte[] bytes = lowClient.read(dlmsIvkCnterDTO.getObis(), ObjectType.forValue(dlmsIvkCnterDTO.getClassId()), dlmsIvkCnterDTO.getAttributeId())[0];
        bytes = packetCodecService.wrapPrefixToPacket(bytes, terminal, deviceAddress);
        deviceConnectService.sendPacket2Channel(channel, bytes);
        byte[] result = terminalChannelService.getSyncResponse(channel, this.connectionTimeoutSec, deviceAddress);
        if (bytes == null) {
            throw new UDISDeviceConException(DeviceConnectErrorCode.HLS_GET_INVOCATION_COUNTER_FAILED.getErrorText());
        }
        UnwrappedPacket unwrappedPacket = packetCodecService.unwrapPrefixPacket(result);
        GXByteBuffer buffer = new GXByteBuffer();
        GXReplyData replyData = new GXReplyData();
        buffer.set(unwrappedPacket.getPacket());

        lowClient.getData(buffer, replyData);
        GXDLMSTranslator translator = new GXDLMSTranslator(TranslatorOutputType.SIMPLE_XML);
        String resultString = translator.dataToXml(replyData.getData());
        CosemData data = CosemXmlUtils.translateCosemXml(resultString);
        return Integer.parseInt(data.getValue(), 16);
    }

    private byte[] getChallengeRequest(AssociationState associationState, Integer invocationCounter) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, SignatureException, InvalidKeyException {
        ((GXDLMSSecureClient) associationState.getClient()).getCiphering().setInvocationCounter(invocationCounter);
        byte[] challenge = associationState.getClient().getApplicationAssociationRequest()[0];
        associationState.setStepNumber(HLSState.challenge.ordinal());
        return challenge;
    }

    private byte[] getLowAssociationRelease(AssociationState associationState) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
        associationState.setStepNumber(HLSState.llsRelease.ordinal());
        return lowClient.releaseRequest()[0];
    }

    private byte[] getInitRequest(AssociationState associationState) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
        boolean isHdlc = associationState.getClient().getInterfaceType().equals(InterfaceType.HDLC);
        if (isHdlc) {
            associationState.setStepNumber(HLSState.snrmRequest.ordinal());
            return associationState.getClient().snrmRequest();
        }
        return getRequestAfterSnrm(associationState);
    }

    private byte[] getRequestAfterSnrm(AssociationState associationState) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
        if (Stream.of(icGetSkipModel).filter(i -> i.equals(associationState.getModelId())).findAny().isPresent()) {
            //跳过获取IC的 dcu直接传IC为1
            byte[] req = getHighAssociateRequest(associationState, ++this.invocationCounter);
            return req;
        }
        byte[] req = getLowAssociateRequest(associationState);
        return req;
    }

    private byte[] getHighAssociateRequest(AssociationState associationState, Integer invocationCounter) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
        if (associationState.getClient() instanceof GXDLMSSecureClient) {
            ((GXDLMSSecureClient) associationState.getClient()).getCiphering().setInvocationCounter(invocationCounter);
        }
        associationState.setStepNumber(HLSState.hlsCreate.ordinal());
        byte[] aa = associationState.getClient().aarqRequest()[0];
        return aa;
    }

    private byte[] getLowAssociateRequest(AssociationState associationState) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
        if (Stream.of(icSAP16Model).filter(i -> i.intValue() == Optional.ofNullable(associationState.getModelId()).orElse(-1L)).findAny().isPresent()) {
            lowClient.setServerAddress(1);
            lowClient.setClientAddress(16);
        } else {
            lowClient.setServerAddress(1);
            lowClient.setClientAddress(1);
        }

        lowClient.setInterfaceType(associationState.getClient().getInterfaceType());
        lowClient.setUseLogicalNameReferencing(associationState.getClient().getUseLogicalNameReferencing());
        lowClient.setMaxReceivePDUSize(0x400);
        Set<Conformance> conformanceList = Sets.newHashSet(Conformance.EVENT_NOTIFICATION, Conformance.ACTION, Conformance.SELECTIVE_ACCESS,
                Conformance.SET, Conformance.GET, Conformance.MULTIPLE_REFERENCES, Conformance.BLOCK_TRANSFER_WITH_ACTION,
                Conformance.BLOCK_TRANSFER_WITH_GET_OR_READ, Conformance.BLOCK_TRANSFER_WITH_SET_OR_WRITE, Conformance.ATTRIBUTE_0_SUPPORTED_WITH_GET
                , Conformance.PRIORITY_MGMT_SUPPORTED);
        lowClient.setProposedConformance(conformanceList);
        byte[] aa = lowClient.aarqRequest()[0];
        associationState.setStepNumber(HLSState.llsCreate.ordinal());
        return aa;
    }

    private byte[] getICRequest(ComEntity com, AssociationState associationState) throws UDISDeviceConException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
        ModelDataItemQueryItem queryItem = new ModelDataItemQueryItem();
        queryItem.setDeviceActionType(DeviceActionType.Get.getCode());
        queryItem.setModelId(associationState.getModelId());
        queryItem.setDataItemIds(Lists.newArrayList(associationState.getIcSubDataItemId()));
        LoggerBuilder.getLogger(logFileNameUtil.getLogFileName(com), this.getClass()).info("try get IC by subDataItemId {}", associationState.getIcSubDataItemId());
        List<DataItemDTO> dataItemDTOList = assetManagementService.queryModelDataItemCosem(queryItem);
        DataItemDTO icDataItem = dataItemDTOList.get(0);
        if (!DLMSSubDataItemDTO.class.isAssignableFrom(icDataItem.getClass())) {
            throw new UDISDeviceConException(DeviceConnectErrorCode.HLS_GET_INVOCATION_COUNTER_FAILED.getErrorText());
        }
        DLMSSubDataItemDTO dlmsIvkCnterDTO = (DLMSSubDataItemDTO) icDataItem;
        byte[] bytes = lowClient.read(dlmsIvkCnterDTO.getObis(), ObjectType.forValue(dlmsIvkCnterDTO.getClassId()), dlmsIvkCnterDTO.getAttributeId())[0];
        associationState.setStepNumber(HLSState.getIC.ordinal());
        return bytes;
    }

}
